/*
*
*	The Gimme ECMAScript Library by Stephen Stchur
*	"Taking the pain out of Javascript!"
*
*	Copyright (c) Microsoft Corporation.  All rights reserved.
*
*	Description:
*	Gimme Query Module:	Gimme's CSS Querying Engine
*
*	Requires:
*	Nothing
*
*/

var Gimme = {};

(function()
{
	Gimme.id = function(_id)
	{
		return document.getElementById(_id);
	};

	Gimme.query = function(_selector, _root, _callback)
	{
		// no selector?  no objects for you!
		if (!_selector) { return []; }
		
		// if it's already an array, just return it
		if (_selector instanceof Array) { return _selector; }
		
		// not a string and not an array?  assume it's a DOM element and return it as the single element in an array
		if (typeof _selector !== 'string') { return [ _selector ]; }
		
		// get ready to match some elements!
		var matched = [];
		
		// normalize the _selector string (collapse all occurrences of 2 or more spaces down to 1 space, and trim beginning and ending white space)
		// also, trim white space from around +, >, and ~ combinators
		_selector = normalize(_selector).replace(/\s*(\+|>|~|,)\s*/g, '$1');
		
		// parse the _selector string into an array of SimpleSelector objects
		var simpleSelectors = parseSelector(_selector);
		
		// all elements matches by the last selector in a complex selector (e.g.  span.cat in "body > div div + p span.cat")
		var numSelectors = simpleSelectors.length;
		var elems = simpleGimme(simpleSelectors[numSelectors - 1], _root);
		
		passed = true;								// indicates whether or not the element has passed all simple selectors 
		
		var tmp,									// all: tmp variable used to execute SimpleGimme during the looping logic
		isPrecedingSibling,							// (~): indicates whether or not the currentElem is a preceding sibling of elem
		elem,										// all: represents the current element during the iteration of elems (all elements returned from the last selector in a complex gimme)
		currentElem,								// all: a copy of elem, used throughout the looping logic because elem itself needs to maintain its value, whereas currentElem may be modified during the loops
		prevSib,									// (+): represents the previous sibling of currentElem (not necessarily of elem)
		siblings,									// (~): all the siblings of elem
		parent,										// (>): represents the parent of currentElem (not necessarily of elem
		ancestor;									// ( ): represents an ancestor of currentElem (could be several generations removed)
		
		var i = 0, j = 0, k = 0, numElems = elems.length;
		while (i < numElems)
		{
			k = 0;
			isPrecedingSibling = true;
			ancestor = currentElem = elem = elems[i];
			
			selectorLoop:
			for (j = numSelectors - 3; j >= 0; j -= 2)
			{
				var combinator = simpleSelectors[j + 1];
				var currSelector = simpleSelectors[j];

				switch (combinator)
				{
					// descendant combinator
					case ' ':
						ancestor = ancestor.parentNode;
						while (ancestor && ancestor !== document)
						{
							if (validateSingleElement(ancestor, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = ancestor;
								continue selectorLoop;
							}
							ancestor = ancestor.parentNode;
						}
						passed = false;
						break selectorLoop;

					// direct descendant (child) combinator
					case '>':
						while (parent !== document)
						{
							parent = currentElem.parentNode;
							if (validateSingleElement(parent, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = parent;
								continue selectorLoop;
							}
							else if (simpleSelectors[j + 3] !== ' ')
							{
								break;
							}
							currentElem = parent;
						}
						passed = false;
						break selectorLoop;

					// adjacent sibling combinator
					case '+':
						prevSib = getSibling(currentElem, -1);
						if (prevSib)
						{
							if (validateSingleElement(prevSib, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = prevSib;
								continue selectorLoop;
							}
						}
						passed = false;
						break selectorLoop;

					// general sibling combinator
					case '~':
						siblings = currentElem.parentNode.childNodes;
						var len = siblings.length;
						while (k < len)
						{
							var sib = siblings[k++];
							if (sib.nodeType === 3)
							{
								continue;
							}
							
							if (sib === elem)
							{
								isPrecedingSibling = false;
								break;
							}							
							
							if (validateSingleElement(sib, currSelector.id, currSelector.tag, currSelector.classes, currSelector.attributes, currSelector.pseudos))
							{
								currentElem = sib;
								continue selectorLoop;
							}
						}
						passed = false;
						break selectorLoop;
				}
			}

			if (!passed)
			{
				elems.splice(i, 1);
				numElems--;
			}
			else
			{
				if (typeof _callback === 'function')
				{
					_callback(g(elem));
				}
				
				i++;			
			}
			passed = true;
		}
		
		_gimmeCallback = null;
		return elems;
	};


	// Helper Area: private members and functions
	//================================
	
	//  SimpleSelector Class (a lightweight object that represents a simple CSS selector (e.g. a selector string that does not involve a combinator))
	function SimpleSelector()
	{
		this.id = null;
		this.tag = null;
		this.classes = [];
		this.attributes = [];
		this.pseudos = [];
		this.isEmpty = true;
	}
	SimpleSelector.prototype.addPart = function(_part, _type)
	{
		this.isEmpty = false;
		switch (_type)
		{
			case 0:
				this.tag = _part.toUpperCase();
				break;
			case 1:
				this.id = _part;
				break;
			case 2:
				this.classes.push(_part);
				break;
			case 3:
				this.attributes.push(parseAttribute(_part));
				break;
			case 4:
				this.pseudos.push(_part);
				break;
		}
		
		return this;
	};
	// End SimpleSelector Class
	
	// private functions
	function parseSelector(_selector)
	{		
		var type = 0;												// type of the current part (0 = tag, 1 = id, 2 = class, 3 = attribute, 4 = pseudo)
		var endings = [ '.#[:', '.[:', '.#[:', ']', '.#[:' ];		// array of string whose characters represent endings for tag, id, class or attribute ( endings[0] for tags, endings[1] for ids, etc...)

						
		var combinators = ' >~+';									// combinators (the 4 characters that can separate simple selectors)
		
		var part = '';												// current part (of a SimpleSelector) that is being built (e.g. span in "span.cat" or zoo in "div#zoo")
		var possibleEndings = endings[type];						// initially, the possible ending characters for a tag
		
		var selectorArray = [];										// the array of SimpleSelector objects to be returned
		var simpleSelector = new SimpleSelector();					// the current SimpleSelector object that is being built 
		
		var c;														// current character in the _selector string
		var i, len = _selector.length;								// looping variable, cached _selector length		
		for (i = 0; i < len; i++)
		{
			c = _selector.charAt(i);
			if (possibleEndings.indexOf(c) !== -1)
			{
				if (part !== '')
				{
					simpleSelector.addPart(part, type);
					part = '';
				}

				switch (c)
				{
					case '#': possibleEndings = endings[type = 1]; continue;
					case '.': possibleEndings = endings[type = 2]; continue;
					case '[': possibleEndings = endings[type = 3]; continue;
					case ']': possibleEndings = endings[type = 0]; continue;
					case ':': possibleEndings = endings[type = 4]; continue;
				}
			}
			else if (type !== 3 && (combinators.indexOf(c) !== -1))
			{
				selectorArray.push(simpleSelector.addPart(part, type));
				selectorArray.push(c);											// in this case, c represents a combinator; we want to capture that!
				part = '';														// reset the part variable to an empty string for the next iteration
				simpleSelector = new SimpleSelector();							// reset the simpleSelector variable for the next iteration
				possibleEndings = endings[type = 0];							// reset the type variable (to tag) for the next iteration, also reset the possibleEndings variable to reflect the new type
			}
			else
			{
				// If the character, c, is not an ending character for the current part type,
				// and it's not a combinator character (when type is not attribute),
				// then just continue building the part
				part += c;
			}
		}

		// Once the entire _selector string has been examined, check the part variable to see if it has data.  If so, add it to the current simpleSelector.
		if (part !== '') { simpleSelector.addPart(part, type); }
		
		// Check the current simpleSelector itself, and if it isn't empty, add it to the selectorArray to be returned
		if (!simpleSelector.isEmpty) { selectorArray.push(simpleSelector); }

		return selectorArray;	
	}
		
	function simpleGimme(_selector, _root)
	{
		var root = _root || document;
		var id = _selector.id;
		var tag = _selector.tag;
		var classes = _selector.classes;
		var attributes = _selector.attributes;
		var pseudos = _selector.pseudos;
		var elem, elems = [], tmp;
		var i, len;
		
		if (id)
		{
			elem = document.getElementById(id);
			return validateSingleElement(elem, null, tag, classes, attributes, pseudos) ? [ elem ] : [];
		}
		else
		{
			tag = tag || '*';
			tmp = root.getElementsByTagName(tag);
			len = tmp.length;
			for (i = 0; i < len; i++) { elems[i] = tmp[i]; }
			filterElements(elems, tag === '*' ? null : tag, classes, attributes, pseudos);
			return elems;
		}
	}
	
	function filterElements(_elems, _tag, _classes, _attributes, _pseudos)
	{
		var elem;
		var i = 0, len = _elems.length;
		while (i < len)
		{
			elem = _elems[i];
			if ((_attributes.length > 0 && !hasAttribute(elem, _attributes)) ||
				(_tag & elem.tagName !== _tag) ||
				(_classes.length > 0 && !hasClass(elem, _classes)) ||
				(_pseudos.length > 0 && !hasPseudo(elem, _pseudos)))
			{
			
				_elems.splice(i, 1);
				len--;
			}
			else
			{
				i++;
			}
		}
	}
	
	function validateSingleElement(_elem, _id, _tag, _classes, _attributes, _pseudos)
	{
		if (!_elem) { return false; }
		if (_tag && _tag !== _elem.tagName) { return false; }
		if (_id && _elem.id !== _id) { return false; }
		if (_attributes.length > 0 && !hasAttribute(_elem, _attributes)) { return false; }
		if (_classes.length > 0 && !hasClass(_elem, _classes)) { return false; }		
		if (_pseudos.length > 0 && !hasPseudo(_elem, _pseudos)) { return false; }
		return true;	
	}
	
	function hasClass(_elem, _classes)
	{
		var className = _elem.className;
		if (typeof className !== 'string')
		{
			className = _elem.getAttribute('class');
		}
		
		if (!className)
		{
			return false;
		}
		 
		var elemClasses = className.split(' ');
		var i, len = _classes.length;
		for (i = 0; i < len; i++)
		{
			if (!contains(elemClasses, _classes[i]))
			{
				return false;
			}
		}
		
		return true;	
	}
	
	function hasAttribute(_elem, _attributes)
	{
		var result = false;
		var i, len = _attributes.length;
		var attr, attrName, attrVal, attrDelim, elemAttr, lio;
		for (i = 0; i < len; i++)
		{
			attr = _attributes[i];
			attrName = attr.name;
			attrVal = attr.val;
			attrDelim = attr.delim;
			elemAttr = readAttribute(_elem, attrName);
			if (!elemAttr)
			{
				continue;
			}
			
			switch (attrDelim)
			{
				case '=':
					result = elemAttr === attrVal;					
					break;
					
				case '^=':
					result = elemAttr.indexOf(attrVal) === 0;
					break;
					
				case '$=':
					lio = elemAttr.lastIndexOf(attrVal);
					result = lio !== -1 && lio + attrVal.length === elemAttr.length;
					break;
					
				case '*=':
					result = elemAttr.indexOf(attrVal) !== -1;
					break;
					
				case '|=':
					result = (elemAttr === attrVal) || (elemAttr.indexOf(attrVal + '-') === 0);
					break;
					
				case '!=':
					result = elemAttr !== attrVal;
					break;
					
				case null:
				default:
					result = elemAttr !== null;
					break;					
			}
			
			if (!result)
			{
				break;
			}
		}

		// only return if the element has all of the specified attributes
		return result;	
	}
	
	function readAttribute(_elem, _attr)
	{
		// IE Bug: when a '#default#userData' behavior is bound to an element, getAttribute to fails to return anything useful.
		// Worse, it throws a run-time error if you try to invoke it with an iFlag parameter, as in: .getAttribute('href', 2);
		// Even more odd, typeof .getAttribute in this case returns "unknown" ( ?? what the heck! )
		// We can leverage this oddity, to avoid invoking .getAttribute(..) with an iFlag parameter.  In that case, we fall back
		// to trying to retrieve the attribute value through the element's .attributes collection.
		if (typeof _elem.getAttribute === 'unknown')
		{
			return _elem.attributes && _elem.attributes[_attr] ? _elem.attributes[_attr].nodeValue : '';
		}

		// If typeof .getAttribute is not "unknown" we can safely use iFlag parameters when invoking .getAttribute(..).
		// It still might return null though, (in IE) so a series of logical ORs will help increase our chances of
		// retrieving the desired attribute value.
		return _elem.getAttribute(_attr, 2) || (_elem.attributes && _elem.attributes[_attr] ? _elem.attributes[_attr].nodeValue : _elem.getAttribute(_attr));
	}
	
	function parseAttribute (_attr)
	{
		var pattern = /\=|\^=|\$=|\*=|\|=|!=/;
		var delim = _attr.match(pattern);
		delim = delim && delim[0];
		var avPair = _attr.split(delim);
		var attr = avPair[0] && trim(avPair[0].replace(/[\[\]"]/g, ''));
		var val = avPair[1] && trim(avPair[1].replace(/[\[\]"]/g, ''));
		return { name: attr, delim: delim, val: val };
	}	
	
	function hasPseudo(_elem, _pseudos)
	{
		var parent, children, numChildren, firstChild, lastChild;
		var result = false;
		var i, j, len = _pseudos.length;
		for (i = 0; i < len; i++)
		{
			parent = _elem.parentNode;
			children = parent.childNodes;
			numChildren = children.length;
			
			switch(_pseudos[i])
			{
				case 'first-child':
					for (j = 0; j < numChildren; j++)
					{
						firstChild = children[j];
						if (firstChild.nodeType === 1)
						{
							break;
						}
					}
					if (firstChild.nodeType !== 1)
					{
						result = false;
					}
					else
					{
						result = firstChild === _elem;
					}
					break;
					
				case 'last-child':
					for (j = numChildren - 1; j >= 0; j--)
					{
						lastChild = children[j];
						if (lastChild.nodeType === 1)
						{
							break;
						}
					}
					if (lastChild.nodeType !== 1)
					{
						result = false;
					}
					else
					{
						result = lastChild === _elem;
					}
					break;
				
				/*
				case 'nth-child:'
					break;
				*/
					
				default:
					result = false;
					break;
			}
			
			if (!result)
			{
				break;
			}
		}
		
		return result;	
	}
	
	function getSibling(_elem, _distance)
	{
		if (_distance === 0)
		{
			return _elem;
		}
		
		var sibling = _distance > 0 ? 'nextSibling' : 'previousSibling';

		var sib = _elem;
		var count = Math.abs(_distance);

		var i = 0;
		while (i < count)
		{
			sib = sib[sibling];
			if (!sib)
			{
				break;
			}
			if (sib.nodeType === 1)
			{
				i++;
			}
		}

		return sib;
	}
	
	var indexOf = function()
	{
		return typeof Array.prototype.indexOf !== 'undefined' ? native_indexOf : custom_indexOf;
		
		function native_indexOf(_arr, _item)
		{
			return _arr.indexOf(_item);
		}
		
		function custom_indexOf(_arr, _item)
		{
			var i, len = _arr.length;
			for (i = 0; i < len; i++)
			{
				if (_arr[i] === _item)
				{
					return i;
				}
			}
			return -1;
		}
	}();
	
	var contains = function()
	{
		return typeof Array.prototype.contains !== 'undefined' ? native_contains : custom_contains;
		
		function native_contains(_arr, _item)
		{
			return _arr.contains(_item);
		}
		
		function custom_contains(_arr, _item)
		{
			return indexOf(_arr, _item) !== -1;
		}
	}();
	
	function trim(_str)
	{
		return _str.replace(/^\s+|\s+$/g, '');
	}
	
	function normalize(_str)
	{
		return trim(_str).replace(/\s{2,}/g, ' ');
	}

	// Expose a few private functions under the "helper" moniker so they can be utilized by the Gimme.object if need be
	Gimme.helper =
	{
		indexOf: indexOf,
		contains: contains,
		trim: trim,
		normalize: normalize,
		hasClass: hasClass,
		readAttribute: readAttribute,
		getSibling: getSibling
	};
	
})();

